<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
<!--  <script src="https://cdn.staticfile.net/vue/3.0.5/vue.global.js"></script>-->
  <script src="https://cdn.staticfile.org/axios/0.27.2/axios.min.js"></script>
  <script src="https://cdn.staticfile.org/vue/3.2.37/vue.global.min.js"></script>
</head>
<!--
组件化传递数据方式 使用props 是一种向组件传递数据的方式,它们是只读的,不可在组件内部修改
创建 一个简单的函数式组件

1.创建React 项目
需要 React 创建一个项目环境,通常会使用 create-react-app 命令创建一个 React 项目

2.定义组件
创建一个简单的函数式组件,它接收name和age作为props,并显示这些信息
子组件 (Child.vue)
<template>
  <div>
    <h1>Hello, {{ name }}!</h1>
    <p>You are {{ age }} years old.</p>
  </div>
</template>

<script setup>defineProps({
  name: {
    type: String,
    required: true,
  },
  age: {
    type: Number,
    default: 18,
  },
});
</script>


定义组件模板:
<template> 包含了组件的 HTML 结构。
定义 props:
使用 defineProps 函数来定义组件接受的 props。
name 必须是一个字符串，并且是必需的。
age 必须是一个数字，默认值为 18。
使用 script setup 语法:
script setup 是 Vue 3 中的一种新特性，它可以简化组件的编写过程，不需要显式地声明 export default。

父组件 (Parent.vue) 在父组件中引入并使用子组件:
<template>
  <div>
    <Child name="Alice" age="25" />
  </div>
</template>

<script setup>import Child from './Child.vue';
</script>

引入子组件:
使用 import 语句从子组件文件导入 Child 组件。
使用子组件:
在 <template> 中使用 <Child> 标签，并传递 name 和 age 作为属性。

但在某些情况下，你可能需要在父组件中注册子组件，例如使用传统的 <script> 标签而不是 script setup。
使用传统的 script标签
<template>
  <div>
    <Child name="Alice" age="25" />
  </div>
</template>

<script>import Child from './Child.vue';

export default {
  components: {
    Child,
  },
};
</script>
引入子组件:
使用 import 语句从子组件文件导入 Child 组件。
注册子组件:
在 <script> 标签中使用 components 对象注册 Child 组件。
使用子组件:
在 <template> 中使用 <Child> 标签，并传递 name 和 age 作为属性。


3.使用propTypes验证props
为了确保传入的props是正确的类型,可以使用propTypes属性进行验证

4.注册组件 vue3中当 使用script setup语法时,组件会自动注册,无需额外的注册步骤.
全局注册组件: 如果一个组件需要在整个应用中多次使用,可以使用全局注册的方式来减少重复代码。
可以通过在应用程序启动时进行,通常在 main.js 或 App.vue 中进行。
import { createApp } from 'vue';
import ParentComponent from './components/ParentComponent.vue';  //导入父组件

const app = createApp(App);

// 全局注册 ParentComponent
app.component('ParentComponent', ParentComponent);  //添加组件

app.mount('#app');


局部注册父组件
如果一个组件仅在某个特定的字组件或者页面中使用,可以使用局部注册的方式来减少重复代码。
<template>
  <div>
    <ChildComponent />
  </div>
</template>

<script setup>
import ChildComponent from './ChildComponent.vue';

// 注意：在 `<script setup>` 中不需要注册组件
</script>

 或者使用传统 `<script>` 标签
<script>import ChildComponent from './ChildComponent.vue';

export default {
  components: {
    ChildComponent,
  },
};
</script>


在 <script setup> 中使用组件
在vue 3中使用 script setup语法,组件会自动注册,无需额外的注册步骤。
 只需要通过 import 语句导入组件。然后就可以在模板中使用即可
 parent.vue 父组件
 <template>
  <div>
    <ChildComponent />
  </div>
</template>

<script setup>import ChildComponent from './ChildComponent.vue';
</script>

总结
当使用 <script setup> 语法糖时，组件会自动注册，无需显式注册。
如果使用传统 <script> 标签，可以在组件选项中使用 components 属性来注册子组件。
如果需要全局注册组件，可以在应用程序启动时进行注册。
通常情况下，父组件不需要特别注册，因为它会在使用它的组件中被引入。如果需要全局注册，可以在启动应用的地方进行。

5. 运行项目
根据构建工具不同 运行项目的命令也会有所不同
1. 使用 Vue Cli (@vue/cli)
使用 npm run serve 命令启动开发服务器。 或者 使用 yarn serve 命令。
yarn serve

2. 使用 Vite 构建工具
使用 npm run dev 命令启动开发服务器。 或者 使用 yarn dev 命令。

3.打包生产版本 当需要发布应用时,可以使用 npm run build 命令来构建生产版本。 或者 使用 yarn build 命令。

总结
开发服务器启动命令通常是 npm run serve 或 npm run dev。
生产构建命令通常是 npm run build。

vue3 项目通用结构 my-vue3-app/
|-- node_modules/
|-- public/
|   |-- index.html
|   |-- favicon.ico
|-- src/
|   |-- main.ts
|   |-- App.vue
|   |-- ...
|-- package.json
|-- vue.config.js
|-- ...

4.清除缓存
npm run serve -- --no-cache
npm run dev -- --clear-screen



项目使用了 TypeScript，并且有 tsconfig.json文件
json 文件
{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": [
    "src/**/*"
  ]
}


5.如果是Vite 确保vite.config.js 文件中的配置正确.确保resolve.alias配置指向src目录
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': '/src',
    },
  },
});

如果还是无法找到app和 main.js文件 可以尝试使用绝对路径来引用文件
import { createApp } from 'vue';
import App from 'E:/work_path/webstorm/WebstormProjects/qianduan/front_demo/my-vue3-app/src/App.vue';
import './src/main.ts';

const app = createApp(App);
app.mount('#app');

6.重启项目
npm run serve 或者 npm run dev

tsconfig.json 文件配置是用来管理 TypeScript 项目的多个配置文件的
这段配置的作用是将当前 tsconfig.json 文件作为一个汇总配置文件，它不直接编译任何文件（因为 files 数组为空），
而是通过 references 字段引用其他更具体的配置文件。这样做的好处是可以将大型项目拆分成多个部分，
每个部分有自己的编译配置，便于管理和维护。

 {
  // files 字段 files数组用来指定哪些文件会被TypeScript编译。(表示只编译 src/index.ts 和 src/utils.ts 这两个文件。)
  "files": [
    "src/index.ts",
    "src/utils.ts"
  ],
  // references 数组用来引用其他tsconfig.json文件 (表示引用了 tsconfig.node.json 和 tsconfig.app.json 这两个配置文件)
  "references": [
    {
    // tsconfig.node.json: 通常用于配置 Node.js 的相关代码，比如后端服务。
      "path": "./tsconfig.node.json"
    },
    {
    // tsconfig.app.json: 通常用于配置前端应用相关的代码，比如 Vue.js 应用。
      "path": "./tsconfig.app.json"
    }
  ]
}

假设你有一个 Vue.js 应用，同时也有一个 Node.js 后端服务。你可以将前端和后端的 TypeScript 代码分开配置，每个部分都有自己的 tsconfig.json 文件。例如：
tsconfig.app.json 可以配置前端应用的 TypeScript 代码。
tsconfig.node.json 可以配置后端服务的 TypeScript 代码。
project/
|-- tsconfig.json
|-- tsconfig.app.json
|-- tsconfig.node.json
|-- src/
|   |-- app/  # 前端代码
|   |   |-- main.ts
|   |   |-- ...
|   |-- server/  # 后端代码
|       |-- index.ts
|       |-- ...
|-- dist/  # 编译输出目录
|-- ...


tsconfig.app.json 可能包含针对前端应用的配置，如：
  {
    "compilerOptions": {
      "target": "esnext",
      "module": "esnext",
      "lib": ["dom", "esnext"],
      "outDir": "./dist/app",
      "strict": true,
      "esModuleInterop": true
    },
    "include": ["src/app/**/*"]
  }

tsconfig.node.json 可能包含针对后端服务的配置，如：
  {
    "compilerOptions": {
      "target": "es2020",
      "module": "commonjs",
      "lib": ["es2020"],
      "outDir": "./dist/server",
      "strict": true,
      "esModuleInterop": true
    },
    "include": ["src/server/**/*"]
  }


{
  // compilerOptions 是一个对象，包含了 TypeScript 编译器的各种选项设置。
  "compilerOptions": {
  // baseUrl 选项用于指定 TypeScript 解析模块的基础路径。
    "baseUrl": "./",
    // paths 选项用于指定模块路径的别名。
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": [
    "src/**/*"
  ]
}
baseUrl 字段
用途: baseUrl 选项用于指定 TypeScript 解析模块的基础路径。
示例: "baseUrl": "./" 表示基础路径是当前目录。
说明: 当使用相对路径导入模块时，TypeScript 会从这个基础路径开始查找。

paths 字段
用途: paths 选项用于指定模块解析的路径映射规则，允许使用自定义的路径别名来引用模块。
示例: "@/*": ["src/*"] 表示所有以 @/ 开头的导入都会被映射到 src/ 目录下的相应文件。
说明: 这种路径映射可以简化代码中的导入语句，使得代码更加整洁易读。

include 字段
用途: include 数组用于指定哪些文件会被 TypeScript 编译器处理。
示例: "include": ["src/**/*"] 表示所有位于 src/ 目录及其子目录下的文件都会被编译。
说明: 这里使用了 glob 模式 **/* 来匹配所有文件。

整体作用
这段配置的作用是告诉 TypeScript 编译器：
基础路径: 设置基础路径为当前目录。
路径映射: 定义了一个路径映射规则，所有以 @/ 开头的路径都会被映射到 src/ 目录下的相应位置。
文件包含: 指定所有位于 src/ 目录及其子目录下的文件都应该被编译。


如果你想在 tsconfig.json 文件中配置 TypeScript 编译器，但又不想让它执行任何编译操作，你可以通过以下方式来实现：
移除 include 和 exclude 字段: 如果你不希望 TypeScript 编译器编译任何文件，可以完全移除 include 和 exclude 字段，或者将它们设置为空数组。
使用 files 字段: 如果你想显式地指定不编译任何文件，可以在 tsconfig.json 文件中使用 files 字段，并将其设置为空数组。
使用 noEmit 选项: 你可以使用 noEmit 选项来告诉 TypeScript 编译器即使在找到要编译的文件时也不生成任何输出文件。
下面是一个示例 tsconfig.json 文件，展示了如何配置 TypeScript 编译器使其不执行任何编译操作：

{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@/*": ["src/*"]
    },
    "noEmit": true  // 添加 noEmit 选项
  },
  "include": [],  // 移除 include 字段或设置为空数组
  "exclude": []   // 移除 exclude 字段或设置为空数组
}

或者，如果你想要使用 files 字段来明确指定不编译任何文件：
{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@/*": ["src/*"]
    },
    "noEmit": true  // 添加 noEmit 选项
  },
  "files": []  // 显式指定不编译任何文件
}
解释
noEmit: 这个选项告诉 TypeScript 编译器即使找到了要编译的文件也不生成任何输出文件。
include 和 exclude: 如果你设置它们为空数组，则 TypeScript 编译器不会编译任何文件。
files: 如果你使用 files 字段并将其设置为空数组，则 TypeScript 编译器同样不会编译任何文件。
注意事项
当使用 noEmit 选项时，TypeScript 仍然会对文件进行类型检查，但不会生成任何输出文件。
如果你使用 files 字段设置为空数组，TypeScript 将不会编译任何文件，也不会进行类型检查。

使用场景
如果你只是想运行类型检查而不生成任何输出文件，可以使用 noEmit 选项。
如果你想要完全禁用 TypeScript 编译器的编译行为，可以使用 files 字段设置为空数组。
或者，在 package.json 文件中添加一个脚本来运行类型检查：
{
  "scripts": {
    "type-check": "tsc --noEmit"
  }
}

compilerOptions 字段
用途: compilerOptions 是一个对象，包含了 TypeScript 编译器的各种选项设置。
baseUrl 字段
用途: baseUrl 选项用于指定 TypeScript 解析模块的基础路径。
示例: "baseUrl": "./" 表示基础路径是当前目录。
说明: 当使用相对路径导入模块时，TypeScript 会从这个基础路径开始查找。
paths 字段
用途: paths 选项用于指定模块解析的路径映射规则，允许使用自定义的路径别名来引用模块。
示例: "@/*": ["src/*"] 表示所有以 @/ 开头的导入都会被映射到 src/ 目录下的相应文件。
说明: 这种路径映射可以简化代码中的导入语句，使得代码更加整洁易读。
noEmit 字段
用途: noEmit 选项告诉 TypeScript 编译器即使找到了要编译的文件也不生成任何输出文件。
示例: "noEmit": true 表示即使找到了要编译的文件，也不生成任何输出文件。
说明: 这个选项可以用来仅运行类型检查而不生成 JavaScript 文件。
include 字段
用途: include 数组用于指定哪些文件会被 TypeScript 编译器处理。
示例: "include": [] 表示没有任何文件会被编译器处理。
说明: 这里设置为空数组，意味着没有任何文件会被包括进来。
exclude 字段
用途: exclude 数组用于指定哪些文件不会被 TypeScript 编译器处理。
示例: "exclude": [] 表示没有任何文件会被排除。
说明: 这里设置为空数组，意味着没有任何文件会被排除。
整体作用
这段配置的作用是告诉 TypeScript 编译器：
基础路径: 设置基础路径为当前目录。
路径映射: 定义了一个路径映射规则，所有以 @/ 开头的路径都会被映射到 src/ 目录下的相应位置。
不生成输出文件: 使用 noEmit 选项来确保即使找到了要编译的文件也不生成任何输出文件。
文件包含/排除: 设置 include 和 exclude 为空数组，意味着没有任何文件会被包括进来或排除出去。



导入必需的模块:
React: React的核心库。
PropTypes: 用于定义props类型的工具。
定义组件:
Greeting 是一个函数式组件，它接收一个对象参数 { name, age }。
组件返回一个JSX结构，其中包含用户的姓名和年龄。
propTypes验证:
Greeting.propTypes 定义了组件期望的props类型。
isRequired 标志表示该prop是必须提供的。
设置默认props:
Greeting.defaultProps 可以指定当某个prop未被传递时的默认值。
导出组件:
使用 export default Greeting 导出组件以便在其他文件中使用。


-->
<!--
计算属性 用于根据其他数据的变化动态计算衍生出来的属性值 ,具有缓存机制 ,只有相关依赖发生变化时才会重新计算
计算属性关键词 computed
computed 函数
  在setup 函数中使用computed函数创建计算属性
  通过箭头函数返回计算的值,该函数会自动跟踪其依赖的响应式数据(state 对象中的name和price).

使用计算属性:
  productName 计算属性衍生自 state.name,每当 state.name 发生变化时,productName 会自动更新
  formattedPrice 计算属性衍生自 state.price,每当 state.price 发生变化时,formattedPrice 会自动更新

响应式数据
  使用 reactive 函数创建state对象 使其成为响应式数据,可以监听其属性变化

方法
  increasePrice 方法用于增加 state.price 的值,每点击一次按钮,state.price 的值会增加1000,并且会更新计算属性 formattedPrice 的值

computed vs methods
使用methods 来代替 computed 效果上两个都是一样的,但是computed 是基于它的依赖缓存 只有相关依赖发生改变时才会重新取值
从实例运行结果看在运行 vm.site = '菜鸟教程 http://www.runoob.com'; 时，setter 会被调用， vm.name 和 vm.url 也会被对应更新。

Vue3 监听属性
监听属性 watch 来响应数据的变化 watch的作用是用于监测响应式属性的变化,并且在属性发生改变时执行特定的操作,他是vue中的一种响应式机制,
允许你在数据发生变化时做出相应的响应,执行自定义的逻辑
watch 使得响应式属性变化时能够有更多的控制



-->
<body>
<!--异步加载中使用watch-->
<div id="watch-example">
  <p>
    <label>
      请输入一个问题,以?号结尾输出答案:
      <input v-model="question">
    </label>
  </p>
  <p>{{answer}}</p>
</div>
<script>
  const watchExampleVM = Vue.createApp({
    data() {
      return {
        question:"",
        answer:"每个问题结尾需要输出?号."
      }
    },
    watch : {
      // 每当问题改变时,此功能将运行,以?号结尾,兼容中英文?
      question(newQuestion, oldQuestion) {
        if (newQuestion.indexOf('?') > -1 || newQuestion.indexOf('？') > -1) {
          this.getAnswer()
        }
      }
    },
    methods: {
      getAnswer() {
        this.answer='加载中...'
        axios.get('/try/ajax/json_vuetest.php').then(response => {
          this.answer = response.data.answer
        })
          .catch(error => {
            this.answer = '错误! 无法访问API' + error
          })
      }
    }
  }).mount('#watch-example')
</script>

<!--watch 实现千米和米之间的换算-->
<!--<div id="app">-->
<!--  <label>-->
<!--    千米:-->
<!--    <input type="text" v-model="kilometers" @focus="currentlyActiveField='Kilometers'">-->
<!--    <br>-->
<!--    米: <input type="text" v-model="meters" @focus="currentlyActiveField='meters'">-->
<!--  </label>-->
<!--</div>-->
<!--<p id="info"></p>-->
<!--<script>-->
<!--  const app = {-->
<!--    data() {-->
<!--      return {-->
<!--        kilometers: 0,-->
<!--        meters:0-->
<!--      }-->
<!--    },-->
<!--    watch: {-->
<!--      kilometers: function (newValue, oldValue){-->
<!--        // 判断是否是输出框-->
<!--        if (this.currentlyActiveField === 'Kilometers'){-->
<!--          this.meters = newValue * 1000-->
<!--        }-->
<!--      },-->
<!--      meters: function (newValue, oldValue){-->
<!--        // 判断是否是输出框-->
<!--        if (this.currentlyActiveField === 'meters'){-->
<!--          this.kilometers = newValue / 1000-->
<!--        }-->
<!--      }-->
<!--    }-->
<!--  }-->
<!--  vm = Vue.createApp(app).mount('#app')-->
<!--  vm.$watch('kilometers',function (newValue,oldValue){-->
<!--    // 这个回调将在 vm.kilometers 发生改变时调用-->
<!--    document.getElementById("info").innerHTML = '当前输入框的值: ' + oldValue + '变为' + newValue;-->
<!--  })-->
<!--</script>-->


<!--watch 实现计数器-->
<!--<div id="app">-->
<!--  <p style="font-size: 25px;">计数器: {{counter}}</p>-->
<!--  <button @click="counter++" style="font-size: 25px;">点我</button>-->
<!--</div>-->

<!--<script>-->
<!--  const app = {-->
<!--    data() {-->
<!--      return {-->
<!--        counter: 1-->
<!--      }-->
<!--    }-->
<!--  }-->
<!--  vm = Vue.createApp(app).mount('#app')-->
<!--  vm.$watch('counter',function (nva1,ova1){-->
<!--    alert('计数器值的变化: '+ova1 + '变为' + nva1 +'!')-->
<!--  })-->
<!--</script>-->
<!--computed 的setter属性-->
<!--<div id="app"></div>-->

<!--<script>-->
<!--  const app = {-->
<!--    data() {-->
<!--      return {-->
<!--        name : 'Baidu',-->
<!--        url: 'https://www.baidu.com'-->
<!--      }-->
<!--    },-->
<!--    computed: {-->
<!--      site : {-->
<!--        // getter-->
<!--        get: function () {-->
<!--          return this.name + '' + this.url-->
<!--        },-->
<!--        // setter-->
<!--        set : function (newValue) {-->
<!--          var names = newValue.split(' ')-->
<!--          this.name = names[0]-->
<!--          this.url= names[names.length - 1]-->
<!--        }-->
<!--      }-->
<!--    }-->
<!--  }-->
<!--  vm = Vue.createApp(app).mount('#app')-->
<!--  document.write('name:' + vm.name);-->
<!--  document.write('<br>')-->
<!--  document.write('url:' + vm.url);-->
<!--  document.write('<br> -&#45;&#45;&#45;&#45;更新数据-&#45;&#45;&#45;&#45;<br>')-->
<!--  // 调用 setter,vm.name 和 vm.url 会同时更新-->
<!--  vm.site = '菜鸟教程 https://www.runoob.com';-->
<!--  document.write('name:' + vm.name);-->
<!--  document.write('<br>')-->
<!--  document.write('url:' + vm.url);-->
<!--</script>-->

<!--methods 在重新渲染的时候,函数总会重新调用执行. computed 是基于它的依赖缓存 只有相关依赖发生改变时才会重新取值-->
<!--<div id="app">-->
<!--  <p>原始字符串 : {{message}}</p>-->
<!--  <p>使用 computed 计算后反转字符串 : {{reversedMessage}}</p>-->
<!--  <p>使用 methods 计算后反转字符串 : {{reverseMessage2()}}</p>-->
<!--</div>-->

<!--<script>-->
<!--  const app = {-->
<!--    data() {-->
<!--      return {-->
<!--        message:"RUNOOB!!"-->
<!--      }-->
<!--    },-->
<!--    computed:{-->
<!--      // 计算属性的getter-->
<!--      reversedMessage : function (){-->
<!--        // 'this' 指向 vm实例-->
<!--        return this.message.split('').reverse().join('')-->
<!--      }-->
<!--    },-->
<!--    methods:{-->
<!--      reverseMessage2(){-->
<!--        return this.message.split('').reverse().join('')-->
<!--      }-->
<!--    }-->
<!--  }-->
<!--  Vue.createApp(app).mount('#app')-->
<!--</script>-->

<!--computed 函数计算属性  -->
<!--<template>-->
<!--  <div>-->
<!--    <p>商品名称: {{productName}}</p>-->
<!--    <p>商品价格: {{formattedPrice}}</p>-->
<!--    <button @click="increasePrice">增加价格</button>-->
<!--  </div>-->
<!--</template>-->
<!--<script>-->
<!--  import {reactive, computed} from 'vue'-->

<!--  export default {-->
<!--    setup() {-->
<!--      // 响应式数据-->
<!--      const state = reactive({-->
<!--        name: '手机',-->
<!--        price: 10000-->
<!--      });-->

<!--      // 计算属性-->
<!--      const formattedPrice = computed(() => {-->
<!--        return '优惠' ${state.name};-->
<!--      });-->

<!--      const formatPrice = computed(() => {-->
<!--        return `¥${state.price.toFixed(2)}`;-->
<!--      });-->

<!--      // 方法-->
<!--      const increasePrice = () => {-->
<!--        state.price += 1000;-->
<!--      };-->

<!--      return {-->
<!--        productName,-->
<!--        formattedPrice,-->
<!--        increasePrice-->
<!--      };-->
<!--    }-->
<!--  };-->
<!--</script>-->

<!--反转字符串示例 使用了属性的实例-->
<!--<div id="app">-->
<!--  <p>原始字符串: {{message}}</p>-->
<!--  <p>反转字符串: {{reversedMessage}}</p>-->
<!--</div>-->
<!--<script>-->
<!--  const app = {-->
<!--    data() {-->
<!--      return {-->
<!--        message: 'RUNOOB!!'-->
<!--      }-->
<!--    },-->
<!--    // 计算属性 新的属性值是根据已有的属性值变化而变化的, 计算属性是reversedMessage 提供的函数将用作属性 vm.reversedMessage的getter-->
<!--    // vm.reversedMessage 依赖于vm.message,在vm.message 发生变化时, vm.reversedMessage 也会跟着变化-->
<!--    computed: {-->
<!--      // 计算属性的 getter-->
<!--      reversedMessage: function () {-->
<!--        // 'this' 指向 vm 实例-->
<!--        return this.message.split('').reverse().join('')-->
<!--      }-->
<!--    }-->
<!--  }-->
<!--  Vue.createApp(app).mount('#app')-->
<!--</script>-->
</body>
</html>
